package com.sparrow.common.util;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.SystemPropertyUtils;

/**
 * 包扫描工具类，用于扫描指定包下的类和方法，支持按注解过滤
 *
 * @author shengbao.Liu
 * @email 646406929@qq.com
 * @time 2016年12月7日 下午4:20:28
 */
public class PackageUtil {

	private static final Logger logger = LoggerFactory.getLogger(PackageUtil.class);

	// 扫描指定包下文件的匹配模式
	private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
	// 类路径前缀
	private static final String CLASSPATH_PREFIX = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
	// 分隔符
	private static final String PATH_SEPARATOR = "/";

	// 资源模式解析器，用于查找符合指定模式的资源
	private static final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
	// 元数据读取工厂，用于读取类的元数据
	private static final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

	/**
	 * 根据指定包路径和注解，查找带有该注解的公共方法集合
	 * 该方法会扫描指定包下的所有类，仅返回公共方法
	 *
	 * @param scanPackages 要扫描的包路径，多个包用逗号分隔
	 * @param annotation   要查找的注解类型
	 * @return 带有指定注解的公共方法集合
	 */
	public static Set<Method> findClassAnnotationMethods(String scanPackages, Class<? extends Annotation> annotation) {
		Set<String> classNames = scanClassNames(scanPackages);
		Set<Method> annotatedMethods = new HashSet<>();
		for (String className : classNames) {
			try {
				Set<Method> methods = findAnnotationMethods(className, annotation);
				if (methods != null) {
					annotatedMethods.addAll(methods);
				}
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
		return annotatedMethods;
	}

	/**
	 * 扫描指定包路径下的所有类名
	 *
	 * @param scanPackages 要扫描的包路径，多个包用逗号分隔
	 * @return 扫描到的类名集合
	 */
	public static Set<String> scanClassNames(String scanPackages) {
		Set<String> validPackages = checkPackage(scanPackages);
		Set<String> classNames = new HashSet<>();
		for (String basePackage : validPackages) {
			if (StringUtils.isBlank(basePackage)) {
				continue;
			}
			String packageSearchPath = buildPackageSearchPath(basePackage);
			try {
				Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
				for (Resource resource : resources) {
					String className = loadClassName(resource);
					if (className != null) {
						classNames.add(className);
					}
				}
			} catch (IOException e) {
				logger.error("扫描类名时发生I/O异常", e);
			}
		}
		return classNames;
	}

	/**
	 * 扫描指定包路径下实现了Handler接口的类，并对每个类执行指定操作
	 *
	 * @param packages 要扫描的包路径数组
	 * @param consumer 对每个找到的类执行的操作
	 */
	public static void scanClass(String[] packages, Consumer<Class<?>> consumer, Predicate<AnnotationMetadata> predicate) {
		for (String basePackage : packages) {
			if (StringUtils.isBlank(basePackage)) {
				continue;
			}
			String packageSearchPath = buildPackageSearchPath(basePackage);
			try {
				Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
				for (Resource resource : resources) {
					processResource(resource, metadata -> {
						if (predicate != null) {
							if (predicate.test(metadata)) {
								try {
									Class<?> clazz = Class.forName(metadata.getClassName());
									consumer.accept(clazz);
								} catch (ClassNotFoundException e) {
									logger.error("类未找到", e);
								}
							}
						} else {
							try {
								Class<?> clazz = Class.forName(metadata.getClassName());
								consumer.accept(clazz);
							} catch (ClassNotFoundException e) {
								logger.error("类未找到", e);
							}
						}
					});
				}
			} catch (IOException e) {
				logger.error("扫描类时发生I/O异常", e);
			}
		}
	}

	/**
	 * 扫描指定包路径下符合指定注解条件的类
	 *
	 * @param scanPackages 要扫描的包路径，多个包用逗号分隔
	 * @param predicate    注解条件判断函数
	 * @return 符合条件的类集合
	 */
	public static Set<Class<?>> scanClass(String scanPackages, Predicate<AnnotationMetadata> predicate) {
		Set<String> validPackages = checkPackage(scanPackages);
		Set<Class<?>> classes = new HashSet<>();
		for (String basePackage : validPackages) {
			if (StringUtils.isBlank(basePackage)) {
				continue;
			}
			String packageSearchPath = buildPackageSearchPath(basePackage);
			try {
				Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
				for (Resource resource : resources) {
					processResource(resource, metadata -> {
						if (predicate == null || predicate.test(metadata)) {
							try {
								Class<?> clazz = Class.forName(metadata.getClassName());
								classes.add(clazz);
							} catch (ClassNotFoundException e) {
								logger.error("类未找到", e);
							}
						}
					});
				}
			} catch (IOException e) {
				logger.error("扫描类时发生I/O异常", e);
			}
		}
		return classes;
	}

	/**
	 * 检查并处理包路径，避免重复扫描父子包
	 *
	 * @param scanPackages 要检查的包路径，多个包用逗号分隔
	 * @return 处理后的有效包路径集合
	 */
	private static Set<String> checkPackage(String scanPackages) {
		if (StringUtils.isBlank(scanPackages)) {
			return new HashSet<>();
		}
		Set<String> packages = new HashSet<>(Arrays.asList(scanPackages.split(",")));
		Set<String> validPackages = new HashSet<>();
		for (String packageName : packages) {
			if (StringUtils.isBlank(packageName) || packageName.equals(".") || packageName.startsWith(".")) {
				continue;
			}
			if (packageName.endsWith(".")) {
				packageName = packageName.substring(0, packageName.length() - 1);
			}
			boolean shouldAdd = true;
			Iterator<String> iterator = validPackages.iterator();
			while (iterator.hasNext()) {
				String existingPackage = iterator.next();
				if (packageName.startsWith(existingPackage + ".")) {
					shouldAdd = false;
					break;
				} else if (existingPackage.startsWith(packageName + ".")) {
					iterator.remove();
				}
			}
			if (shouldAdd) {
				validPackages.add(packageName);
			}
		}
		return validPackages;
	}

	/**
	 * 构建包搜索路径
	 *
	 * @param basePackage 基础包名
	 * @return 构建好的包搜索路径
	 */
	public static String buildPackageSearchPath(String basePackage) {
		return CLASSPATH_PREFIX +
				org.springframework.util.ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) +
				PATH_SEPARATOR + DEFAULT_RESOURCE_PATTERN;
	}

	/**
	 * 从资源中加载类名
	 *
	 * @param resource 类资源
	 * @return 类名，如果读取失败则返回null
	 */
	private static String loadClassName(Resource resource) {
		try {
			MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
			if (resource.isReadable() && metadataReader != null) {
				return metadataReader.getClassMetadata().getClassName();
			}
		} catch (IOException e) {
			logger.error("加载类名时发生I/O异常", e);
		}
		return null;
	}

	/**
	 * 查找指定类中带有指定注解的公共方法
	 *
	 * @param fullClassName 类的全限定名
	 * @param annotation    要查找的注解类型
	 * @return 带有指定注解的公共方法集合
	 * @throws ClassNotFoundException 如果类未找到
	 */
	public static Set<Method> findAnnotationMethods(String fullClassName, Class<? extends Annotation> annotation) throws ClassNotFoundException {
		Set<Method> methods = new HashSet<>();
		Class<?> clazz = Class.forName(fullClassName);
		Method[] declaredMethods = clazz.getDeclaredMethods();
		for (Method method : declaredMethods) {
			if (Modifier.isPublic(method.getModifiers()) && method.isAnnotationPresent(annotation)) {
				methods.add(method);
			}
		}
		return methods;
	}

	/**
	 * 处理资源，读取元数据并执行操作
	 *
	 * @param resource 资源
	 * @param action   要执行的操作
	 */
	private static void processResource(Resource resource, Consumer<AnnotationMetadata> action) {
		if (!resource.isReadable()) {
			return;
		}
		try {
			MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
			if (metadataReader != null) {
				AnnotationMetadata metadata = (AnnotationMetadata) metadataReader.getClassMetadata();
				action.accept(metadata);
			}
		} catch (IOException e) {
			logger.error("处理资源时发生I/O异常", e);
		}
	}

}